#pragma once
#include "CScrLanguage.h"
#include "CFileSystem.h"
#include "CProjects.h"
#include "CTokens.h"

class CProject;
class CProjectFile;
class CScript;
class CSubScript;
class CScriptFile;
class CScriptOutput;
class CScriptHeader;
class CScope;

class CScriptVariables
{
	friend class CSubScript;

protected:
	// Binary tree list - faster random-access
	CList<CVariable>			m_Variables;

	// Linked list - faster ordered access
	CLListEx<CVariable*>		m_VariableList;

	// Counter
	int						m_nNumVariables;
	int						m_nNextVarIndex;

public:
	CScriptVariables()	:	m_nNumVariables(0),
							m_nNextVarIndex(0)
	{
		m_Variables.Clear();
	}

	~CScriptVariables()
	{
		m_Variables.Clear();
	}
	
	void Add(CVariable *pVariable)
	{
		m_Variables.Add(pVariable->GetHash(), pVariable);
		m_VariableList.Add(pVariable);
		
		if(pVariable->IsLocal() && !pVariable->IsPredefined())
		{
			pVariable->SetIndex(m_nNextVarIndex);
			m_nNextVarIndex += pVariable->GetSize();
		}

		m_nNumVariables++;
	}
	inline CVariable		*	GetVariable(const char * szName)				{return m_Variables.Get(GenerateHash(szName));}
	inline CVariable		*	GetVariable(DWORD dwHash)					{return m_Variables.Get(dwHash);}
	inline CVariable		*	GetNextVariable(const char * szName)			{return m_Variables.Next();}
	inline CVariable		*	GetNextVariable(DWORD dwHash)				{return m_Variables.Next();}
	inline DWORD				GetNumVariables()							{return m_nNumVariables;}
};
class CScriptLabels
{
	friend class CSubScript;

protected:
	CList<CLabel>			m_Labels;
	CLListEx<CLabel*>		m_LabelList;
	int						m_nNumLabels;

public:
	CScriptLabels()			:	m_nNumLabels(0)
	{
		m_Labels.Clear();
	}
	~CScriptLabels()
	{
		m_Labels.Clear();
		m_LabelList.Clear();
	}
	
	void Add(CLabel *pLabel)
	{
		m_Labels.Add(pLabel->GetHash(), pLabel);
		m_LabelList.Add(pLabel);
		m_nNumLabels++;
	}
	CLabel * GetLabelByOffset(DWORD dwOffset)
	{
		for(auto pLink = m_LabelList.Begin(); pLink != m_LabelList.Bad(); pLink++)
			if(pLink->GetOffset() == dwOffset) return *pLink;
		return nullptr;
	}
	inline CLabel	*	GetLabel(const char * szName)				{return m_Labels.Get(GenerateHash(szName));}
	inline CLabel	*	GetLabel(DWORD dwHash)						{return m_Labels.Get(dwHash);}
	inline CLabel	*	GetNextLabel(const char * szName)			{return m_Labels.Next();}
	inline CLabel	*	GetNextLabel(DWORD dwHash)					{return m_Labels.Next();}
	inline int			GetNumLabels()								{return m_nNumLabels;}
};
class CScriptConstants
{
	friend class CSubScript;

protected:
	CList<CConstant<int>>		m_Constants;
	CLListEx<CConstant<int>*>	m_ConstantList;
	int							m_nNumConstants;

public:
	CScriptConstants()	:	m_nNumConstants(0)
	{
		m_Constants.Clear();
	}
	~CScriptConstants()
	{
		m_Constants.Clear();
	}

	void Add(CConstant<int> *pConstant)
	{
		m_Constants.Add(pConstant->GetHash(), pConstant);
		m_ConstantList.Add(pConstant);
		m_nNumConstants++;
	}
	inline CConstant<int> *	GetConstant(const char * szName)			{return m_Constants.Get(GenerateHash(szName));}
	inline CConstant<int> *	GetConstant(DWORD dwHash)					{return m_Constants.Get(dwHash);}
	inline CConstant<int> *	GetNextConstant(const char * szName)		{return m_Constants.Next();}
	inline CConstant<int> *	GetNextConstant(DWORD dwHash)				{return m_Constants.Next();}
	inline int				GetNumConstants()							{return m_nNumConstants;}
};
class CScriptIdentifiers : public CScriptVariables, public CScriptLabels, public CScriptConstants
{
	friend class CSubScript;

	CScriptIdentifiers() : CScriptVariables(), CScriptLabels(), CScriptConstants()
	{}
};

class CScriptFile : public CFile
{
	
public:
	CScriptFile(CDirectory *pDirectory, const char * szFilePath, eFileMode mode, eFileType type = FILE_TYPE_BINARY) :
		CFile(pDirectory, szFilePath, mode, type)
	{
	}
	CScriptFile(const char * szFilePath, eFileMode mode, eFileType type = FILE_TYPE_BINARY) :
		CFile(szFilePath, mode, type)
	{
	}
	~CScriptFile()
	{
	}
};

enum eParseVarType
{
};

class CScriptParseVar : public CIdentifier
{
	friend class CScriptParseInfo;

	eParseVarType		m_eType;

	CScriptParseVar(const char * szName, eParseVarType eType) : CIdentifier(szName),
		m_eType(eType)
	{
	}
};

class CScriptParseInfo : CLListEx<const CToken*>
{
	// script parse info (except identifiers)
	CSubScript			*	m_pSubScript;							// the subscript this was taken off
	__int64					m_nTimeParsed;							// when the parse of the script was finished
	char					m_szScriptName[MAX_SCRIPT_NAME];		// any SCRIPT_NAME value or "noname%d"
	bool					m_bIsNamed;								// SCRIPT_NAME officially occurs?
	bool					m_bIsMission;							// is a mission script?
	DWORD					m_dwBeginOffset;						// previous_begin_offset + sizeof(previous)
	DWORD					m_dwSize;								// sizeof(current)
	DWORD					m_dwHash;								// ????
	CList<CScriptParseVar>	m_ParseVars;
	CList<const CToken>		m_TokenList;

public:
	CScriptParseInfo(CSubScript * pSubScript)
	{
		*m_szScriptName = '\0';
		m_pSubScript = pSubScript;
	}

	CScriptParseVar			*	AddVar(const char * szName, eParseVarType eType)
	{
		auto pParseVar = new CScriptParseVar(szName, eType);
		if(pParseVar) m_ParseVars.Add(pParseVar->GetHash(), pParseVar);
		return pParseVar;
	}
	CScriptParseVar			*	GetVar(DWORD dwHash) const			{return m_ParseVars.Get(dwHash);}
	CScriptParseVar			*	GetVar(const char * szName) const	{return GetVar(GenerateHash(szName));}
	void						AddToken(const CToken * pToken)
	{
		DWORD dwHash;

		Add(pToken);

		switch(pToken->GetType())
		{
		case TOKEN_COMMAND:
			dwHash = pToken->GetCommandValue()->GetHash();
			break;
		case TOKEN_VAR:
			dwHash = pToken->GetVariableValue()->GetHash();
			break;
		case TOKEN_LABEL:
			dwHash = pToken->GetLabelValue()->GetHash();
			break;
		default:
			if(pToken->GetStringLength() > 1 && CIdentifiers::IsValidIdentifier(pToken->GetString()))
			{
				dwHash = GenerateHash(pToken->GetString(), pToken->GetStringLength());
			}
			else return;
			break;
		}
		
		m_TokenList.Add(dwHash, pToken);
	}

	inline void									ClearTokenList()
	{
		m_TokenList.Clear();
		Clear();
	}
	inline const CToken						*	FindToken(const char * szName, DWORD dwHash)	{return m_TokenList.Get(dwHash);}
	inline const CToken						*	FindToken(const char * szName)					{return FindToken(szName, GenerateHash(szName));}
	inline const CToken						*	FindNextToken()									{return m_TokenList.Next();}
	inline CLListEx<const CToken *>::ILink		GetFirstToken()		{return Begin();}
	inline CLListEx<const CToken *>::ILink		GetLastToken()		{return Bad();}

	inline CSubScript		*	GetScript()						{return m_pSubScript;}
	inline __int64				GetTimeParsed()					{return m_nTimeParsed;}
	inline void					SetTimeParsed(__int64 v)		{m_nTimeParsed = v;}
	inline bool					IsNamed()						{return m_bIsNamed;}
	inline bool					IsMission()						{return m_bIsMission;}
	inline DWORD				GetBeginOffset()				{return m_dwBeginOffset;}
	inline void					SetBeginOffset(DWORD v)			{m_dwBeginOffset = v;}
	inline DWORD				GetSize()						{return m_dwSize;}
	inline void					SetSize(DWORD v)				{m_dwSize = v;}
	inline const char		*	GetName()						{return *m_szScriptName ? m_szScriptName : nullptr;}
	inline void					SetName(const char * szName)	{szncpy(m_szScriptName, szName, sizeof(m_szScriptName));}

	void						OnParse();
};

class CScope : private CScriptVariables
{
	friend class CScript;
	friend class CSubScript;
	
	CSubScript			*	m_pSubScript;
	int						m_nStartLine;
	int						m_nEndLine;
	bool					m_bIsActive;

	CScope(CSubScript *pScript, int nLine)
	{
		m_pSubScript = pScript;
		m_nStartLine = nLine;
		m_nEndLine = -1;
		m_bIsActive = true;
	}
	~CScope()
	{
	}

	void	Add(CVariable *pVariable)
	{
		if(GetVariable(pVariable->GetName()))
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1136"), NULL, 0, pVariable->GetName()));
		return CScriptVariables::Add(pVariable);
	}

	CVariable * Find(const char * szName)
	{
		DWORD dwHash = GenerateHash(szName);
		auto pVariable = GetVariable(dwHash);
		while(pVariable && !pVariable->IsNamed(szName, dwHash))
			pVariable = GetNextVariable(dwHash);
		return pVariable;
	}
	
	void Close(int nLine)
	{
		m_nEndLine = nLine;
		m_bIsActive = false;
	}
};

class CSubScript : public CScriptIdentifiers, public virtual CProjectFile
{
	friend class CScript;

	CDirectory				*	m_pScriptDir;
	CScript					*	m_pScript;
	CScriptFile				*	m_pScriptFile;
	CScriptParseInfo		*	m_pParseInfo;
	CScope					*	m_pScope;
	CLListEx<CScriptFile*>		m_IncludeFiles;
	bool						m_bLastParseSuccess;
	bool						m_bLastParseFail;
	int							m_nScopeOpenLine;
	int							m_nNumLines;
	char						m_szInPath[MAX_PATH];
	char						m_szOutPath[MAX_PATH];
	__int64						m_nLastParseTime;

	inline void Init()
	{
		if(!m_pParseInfo) m_pParseInfo = new CScriptParseInfo(this);
		if(m_pScope) delete m_pScope;
		m_pScope = nullptr;
		m_nNumLines = 0;
		m_nLastParseTime = 0;
		*m_szOutPath = '\0';
	}

	// Main subscript (CScript only)
	CSubScript(CScript * pScript, const char * szFilePath)	:	CScriptIdentifiers(),
																m_pScript(pScript),
																m_pParseInfo(nullptr),
																m_pScope(nullptr),
																m_nLastParseTime(0)
	{
		strcpy(m_szOutPath, szFilePath);
		char * pStr = strrchr(m_szOutPath, '\\');
		if(pStr) *pStr = '\0';
		else GetWorkingDir(m_szOutPath, sizeof(m_szOutPath));
		m_pScriptDir = DirManager.Add(m_szOutPath);
		szncpy(m_szInPath, szFilePath, sizeof(m_szInPath));
		m_pScriptFile = new CScriptFile(m_pScriptDir, m_szInPath, FILE_MODE_READ, FILE_TYPE_TEXT);
		if(!m_pScriptFile->OK())
			throw(SCRambl.BuildText(SCRambl.GetText("GENERAL", "OERR"), NULL, 0, szFilePath));
		
		BuildEvents[PARSE][AFTER] = &OnParseEnd;
		BuildEvents[PARSE][BEFORE] = &OnParse;
	}
	~CSubScript()
	{
		delete m_pParseInfo;
		delete m_pScriptDir;
		delete m_pScriptFile;
		delete m_pScope;
	}

	// Meh...
	/*void LoadPresetVars()
	{
		if(CVariable *pVariable = CVariables::GetFirstVar())
		{
			do
				if(pVariable->IsPredefined()) Add(pVariable);
			while(pVariable = CVariables::GetNextVar());
		}
	}*/

public:
	void						Add(CVariable *);
	void						Add(CLabel *);
	void						Add(CToken *);
	void						OpenScope(int nLine)
	{
		if(m_pScope) throw(SCRambl.GetText("ERRORS", "E1137"));
		m_pScope = new CScope(this, nLine);
		m_nScopeOpenLine = nLine;
	}
	void						CloseScope(int nLine)
	{
		if(!m_pScope) throw(SCRambl.GetText("ERRORS", "E1138"));
		delete m_pScope;
		m_pScope = nullptr;
	}
	CScriptFile				*	GetLastModifiedFile()
	{
		CScriptFile	* pLatestFile = nullptr;
		for(auto pLink = m_IncludeFiles.Begin(); pLink != m_IncludeFiles.Bad(); ++pLink)
		{
			if(pLatestFile && pLink->GetModifiedTime() >= pLatestFile->GetModifiedTime()) pLatestFile = *pLink;
		}
		return pLatestFile && pLatestFile->GetModifiedTime() > m_pScriptFile->GetModifiedTime() ? pLatestFile : m_pScriptFile;
	}
	inline bool					IsScopeOpen()							{return m_pScope != nullptr;}
	inline DWORD				Size()									{return m_pParseInfo->GetSize();}
	inline void					LastParseTime(__int64 nTime)			{m_nLastParseTime = nTime;}
	inline __int64				LastParseTime()							{return m_nLastParseTime;}
	inline bool					IsParseNeeded()							{return GetLastModifiedFile()->GetModifiedTime() > m_nLastParseTime;}
	inline CScript			*	GetScript()								{return m_pScript;}
	inline CScriptFile		*	GetFile()								{return m_pScriptFile;}
	inline CScriptParseInfo	*	GetParseInfo()							{return m_pParseInfo;}
	inline void					SetFile(CScriptFile * pScriptFile)		{m_pScriptFile = pScriptFile;}
	inline void					SetOutFilePath(const char * szPath)		{strncpy(m_szOutPath, szPath, sizeof(m_szOutPath));}
	static void					OnParse();
	static void					OnParseEnd();
	void							Parse();
	void							Compile();
	void							Build();
	void							Copy();
};

class CScriptHeader : public CLListEx<const CToken*>
{
	DWORD								m_dwSize;

public:
	/*enum eHeaderCommand
	{
		HEADER_CMD_MOV,
		HEADER_CMD_VAR_SPACE,
		HEADER_CMD_MODEL_NAMES,
		HEADER_CMD_MISSION_OFFSETS,
		HEADER_CMD_STREAMED_SCRIPT_INFO,
	};
	enum eHeaderVar
	{
		HEADER_VAR_NONE,
		HEADER_VAR_SEGID,
		HEADER_VAR_TARGETGAME,
		HEADER_VAR_MAINSIZE,
		HEADER_VAR_LARGESTMISSIONSIZE,
		HEADER_VAR_NUMMISSIONS,
		HEADER_VAR_NUMMODELS,
		HEADER_VAR_HIGHESTNUMLVARS,
		HEADER_VAR_LARGESTSTREAMEDSCRIPTSIZE,
		HEADER_VAR_NUMSTREAMEDSCRIPTS,
		HEADER_VAR_VARSPACESIZE,
	};

	class CHeaderData
	{
		friend class CScriptHeader;
		eHeaderCommand	m_eCmd;
		eHeaderVar		m_eVar;
		size_t			m_nSize;
		int				m_nVal;
	};
	class CSegment
	{
		friend class CScriptHeader;
		int								m_nSegID;
		CLListEx<CHeaderData*>			m_Data;

	public:
		CSegment(int nSegID)	:	m_nSegID(nSegID)
		{
		}
	};
	
	static CLListEx<CSegment*>			s_Segments;
	CLListEx<CToken*>					m_Tokens;
	DWORD								m_dwSize;

public:
	static bool			LoadFromFile(const char *szFilePath);*/
};

class CScript : public CSubScript
{
	// Only allowed output on top-level scripts
	CScriptFile				*	m_pOutput;
	CSubScript				*	m_pCurrentSubScript;
	CScriptHeader			*	m_pHeader;
	
	CLListEx<CSubScript*>		m_SubScripts;
	int							m_nNumFiles;
	int							m_nNumSubScripts;
	DWORD						m_dwSize;
	char						m_szFileDir[MAX_PATH];

	void Init()
	{
		m_nNumFiles = 0;
		m_nNumSubScripts = 0;
		m_pOutput = nullptr;
		m_pHeader = nullptr;
	}

public:
	CScript(const char * szFileInPath) : CSubScript(this, szFileInPath)
	{
		Init();
		m_pCurrentSubScript = this;
		GetFileDirectory(szFileInPath, m_szFileDir);
		if(!*m_szFileDir) GetWorkingDir(m_szFileDir, sizeof(m_szFileDir));
		SCRambl.SetProjectDirectory(m_szFileDir);
		if(*m_szFileDir) strcat(m_szFileDir, "\\");
		IsMergedWith(true);
	}	
	~CScript()
	{
		m_SubScripts.Clear();
	}

	inline const char		*	GetDirectoryPath()				{return m_szFileDir;}
	inline CSubScript		*	GetSubScript()					{return m_pCurrentSubScript;}
	inline CScriptHeader		*	GetHeader()						{return m_pHeader;}
	inline DWORD					GetCompileSize()					{return m_dwSize;}
	inline void					SetCompileSize(DWORD n)			{m_dwSize = n;}
	void							AddHeader()
	{
		if(m_pHeader) delete m_pHeader;
		m_pHeader = new CScriptHeader();
	}

	void						OnParse();

	void						Add(CVariable *pVariable)
	{
		if(pVariable->IsLocal())
			return CSubScript::Add(pVariable);		// redir to right one
		
		if(CVariable *pTmpVar = FindVariable(pVariable->GetName()))
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1138"), NULL, 0, "%s%d", pTmpVar->GetName(), pTmpVar->GetDeclarationLine()));
			
		if(GetNumVariables() >= CLimits::GetMaxVar())
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1135"), NULL, 0, "%d%s", CLimits::GetMaxVar(), "VAR"));
		CScriptVariables::Add(pVariable);
	}
	void						Add(CLabel *pLabel)
	{
		//if(GetSubScript() != this)
		//	GetSubScript()->Add(pLabel);
		m_Labels.Add(pLabel->GetHash(), pLabel);
		m_LabelList.Add(pLabel);
	}
	CVariable				*	FindVariable(const char * szName)
	{
		int hash;
		// Handle array
		if(const char *astr = strchr(szName, '['))
			hash = GenerateHash(szName, astr - szName);
		else hash = GenerateHash(szName);

		CVariable * pVariable = nullptr;

		// Try the local scope or take it global
		if(m_pScope)
		{
			// Find var matching hash, then verify name
			pVariable = m_pScope->GetVariable(hash);
			while(pVariable && !pVariable->IsNamed(szName, hash))
				pVariable = m_pScope->GetNextVariable(hash);
		}
		if(!pVariable)
		{
			pVariable = m_Variables.Get(hash);
			while(pVariable && !pVariable->IsNamed(szName, hash))
				pVariable = m_Variables.Next();
		}
		return pVariable;
	}
	CSubScript				*	OpenSubScript(const char * szFilePath)
	{
		auto pSubScript = new CSubScript(this, szFilePath);
		m_SubScripts.Add(pSubScript);
		m_pCurrentSubScript = pSubScript;
		return pSubScript;
	}
	CSubScript				*	GetSubScript(const char * szFileName)
	{
		for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
		{
			if(!strcmp(szFileName, pLink->GetFile()->GetName()))
				return *pLink;
		}
		return nullptr;
	}
	CLabel					*	FindLabel(const char * szName)
	{
		return FindLabel(szName, GenerateHash(szName));
	}
	CLabel					*	FindLabel(const char * szName, DWORD dwHash)
	{
		CLabel * pLabel = m_Labels.Get(dwHash);
		while(pLabel && !pLabel->IsNamed(szName, dwHash))
			pLabel = m_Labels.Next();
		return pLabel;
	}
	
	void						Parse();
	void						Compile();
	void						Build();
	void						Merge();
	void						Copy();
};